package com.liuqianfei.mdurl;

import com.liuqianfei.util.ArrayUtil;
import com.liuqianfei.util.StringUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author liuqianfei
 */
public class Decode
{
    /* eslint-disable no-bitwise */
    Map<String, String[]> decodeCache = new HashMap<>();
    String defaultChars   = ";/?:@&=+$,#";
    // String componentChars = "";

    public String[] getDecodeCache(String exclude)
    {
        char ch;
        String[] cache = decodeCache.get(exclude);

        if (null != cache && cache.length > 0)
            return cache;

        cache = new String[128];

        for (int i = 0; i < 128; i++)
        {
            ch = (char) i;
            cache[i] = String.valueOf(ch);
        }

        for (int i = 0; i < exclude.length(); i++)
        {
            ch = exclude.charAt(i);
            cache = ArrayUtil.set(cache, ch, "%" + StringUtil.slice(("0" + Integer.toHexString(ch).toUpperCase()), -2));
        }

        decodeCache.put(exclude, cache);
        return cache;
    }


    // Decode percent-encoded string.
    //
    public String decode(String string, String exclude)
    {
        String[] cache;

        if (null == exclude)
            exclude = this.defaultChars;

        cache = getDecodeCache(exclude);

        String regexp = "(%[a-f0-9]{2})+";
        Pattern pattern = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(string);
        while (matcher.find())
        {
            String seq = matcher.group();

            int i, l, b1, b2, b3, b4, chr;
            String result = "";

            for (i = 0, l = seq.length(); i < l; i += 3)
            {
                b1 = Integer.parseInt(StringUtil.slice(seq, i + 1, i + 3), 16);

                if (b1 < 0x80)
                {
                    result += cache[b1];
                    continue;
                }

                if ((b1 & 0xE0) == 0xC0 && (i + 3 < l))
                {
                    // 110xxxxx 10xxxxxx
                    b2 = Integer.parseInt(StringUtil.slice(seq, i + 4, i + 6), 16);

                    if ((b2 & 0xC0) == 0x80)
                    {
                        chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F);

                        if (chr < 0x80)
                            result += '\ufffd' + "" + '\ufffd';
                        else
                            result += String.valueOf((char) chr);

                        i += 3;
                        continue;
                    }
                }

                if ((b1 & 0xF0) == 0xE0 && (i + 6 < l))
                {
                    // 1110xxxx 10xxxxxx 10xxxxxx
                    b2 = Integer.parseInt(StringUtil.slice(seq, i + 4, i + 6), 16);
                    b3 = Integer.parseInt(StringUtil.slice(seq, i + 7, i + 9), 16);

                    if ((b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80)
                    {
                        chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F);

                        if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF))
                            result += '\ufffd' + "" + '\ufffd' + "" + '\ufffd';
                        else
                            result += String.valueOf((char) chr);

                        i += 6;
                        continue;
                    }
                }

                if ((b1 & 0xF8) == 0xF0 && (i + 9 < l))
                {
                    // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx
                    b2 = Integer.parseInt(StringUtil.slice(seq, i + 4, i + 6), 16);
                    b3 = Integer.parseInt(StringUtil.slice(seq, i + 7, i + 9), 16);
                    b4 = Integer.parseInt(StringUtil.slice(seq, i + 10, i + 12), 16);

                    if ((b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80 && (b4 & 0xC0) == 0x80)
                    {
                        chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F);

                        if (chr < 0x10000 || chr > 0x10FFFF)
                        {
                            result += '\ufffd' + "" +'\ufffd' + "" + '\ufffd' + "" + '\ufffd';
                        }
                        else
                        {
                            chr -= 0x10000;
                            result += String.valueOf((char) (0xD800 + (chr >> 10))) + String.valueOf((char) (0xDC00 + (chr & 0x3FF)));
                        }

                        i += 9;
                        continue;
                    }
                }

                result += '\ufffd';
            }

            string = string.replaceFirst(seq, result);
        }

        return string;
    }
}
